/**
 * Camera.cpp
 *
 * Author:
 *   Chris Herbison
 *
 * Description:
 *   Represents the camera for the scene.
 */

//#define DEBUG

#ifdef GDD_LAB_COMPUTER
#include <glut.h>
#else
#include <gl/glut.h>
#endif

#include "Camera.h"
#include "MyColor.h"
#include "IntersectData.h"
#include "Triangle.h"
#include "WorldData.h"
#include "PhongI.h"
#include "KDTreeNode.h"

#include <fstream>
#include <iostream>

/*
 * Paints a pixel at a given point a given color.
 *
 * x - x coordinate of pixel.
 * y - y coordinate of pixel.
 * color - Color to make pixel.
 */
void drawPixel(int x, int y, MyColor color){
     glColor3f(color.r, color.g, color.b);
     glRectd(x, y, x+1, y+1);
}

/*
 * Calculates the luminance term of a color.
 *
 * lColor - The color to calculate the luminance of.
 *
 * return (double) - The luminance of the color.
 */
double Camera::luminance(MyColor lColor){
     double Rxy = lColor.r;
     double Gxy = lColor.g;
     double Bxy = lColor.b;
     return 0.27*Rxy+0.67*Gxy+0.06*Bxy;
}

/*
 * The constructor.
 *
 * _position - The position of the camera in world space.
 * _lookAt - The point that the camera points towards.
 * _up - The orientation of the camera.
 * _Lmax - Scale to multiply by for tone reproduction.
 * toneType - The type of tone reproduction to use {t/f} = {Ward/Reinhard}
 * _Lwa - Pre determined value for Lwa (only to be used in Reinhard tone reproduction).
 * _toneA - Percentage for gray zone (as used in Reinhard tone reproduction).
 */
Camera::Camera(Point3 _position, Vector3 _lookAt, Vector3 _up, int _screenWidth, int _screenHeight, ToneReproductionModel* _toneModel):
               position(_position), lookAt(_lookAt), up(_up), screenWidth(_screenWidth), screenHeight(_screenHeight), toneModel(_toneModel){
}

/*
 * Renders the scene given a world.
 *
 * world - The world to render.
 */
void Camera::render(World w){
    //Load all the lights in to the data object, as they won't be
    //     changing unless the scene needs to be rendered again.
    IntersectData data;
    WorldData staticData;
    PhongI shader;
    for(int i = 0; i < (int)w.worldLights.size(); i++){
	    staticData.lights.push_back(w.worldLights.at(i));
    }
    for(int i = 0; i < (int)w.worldObjects.size(); i++){
        staticData.objects.push_back(w.worldObjects.at(i));
    }
	staticData.tree = w.worldTree;
	
    for(int i = 0; i < (int)w.worldRays.size(); i++){
#ifdef DEBUG
		cout << (((float)i)/((float)w.worldRays.size()))*100 << "% complete" << endl;
#endif
        //Keep track of intersections on this ray for z buffering algorithm later.
        vector<Point3> intersectPoints;
        vector<MyColor> intersectColors;

        //Load in to local variable to avoid too many access calls.
        Ray currentRay(w.worldRays.at(i));
        //Calculate the location of the pixel relative to the ray.
        Point3 pixelPoint(currentRay.direction.x+(screenWidth/2.0), currentRay.direction.y+(screenHeight/2.0), 0);
        //Pass the viewing vector to the IntersectData class to avoid doing this several times later.
        data.view = currentRay.direction;

		//Get variables for traversing K-D Tree
		vector<Point3> boxIntersections = w.worldTree->root.AABB.rayIntersections(currentRay);

		if(boxIntersections.size() >= 2){
			vector<Triangle*> curTriangles = w.worldTree->getPossibleIntersections(currentRay, boxIntersections.at(0), boxIntersections.at(1));
			//vector<Object*> curTriangles = w.worldObjects;

			//Check if this ray hit any objects.
			for(int j = 0; j < (int)curTriangles.size(); j++){
		    //Reset the parity of the object (for transmission rays).
				curTriangles.at(j)->parity = false;
				if(curTriangles.at(j)->intersects(currentRay, &data)){
					//If a ray hits something, shade that pixel given the intersection data, and add it to the vector of intersections.
                    MyColor shadedColor = shader.illuminate(data, curTriangles.at(j), staticData, j, 0);
                    intersectPoints.push_back(data.pos);
                    intersectColors.push_back(shadedColor);
				}
			}
			//Sort all intersections from closest to farthest z value.
			/*for(int j = 0; j < (int)intersectPoints.size(); j++){
			   int lowest = j;
               for(int k = j; k < (int)intersectPoints.size(); k++){
                    if(k != lowest && intersectPoints.at(k).z < intersectPoints.at(lowest).z){
                         lowest = k;
                    }
               }
               if(lowest != j){
                    Point3 tSwapP(intersectPoints.at(j));
                    MyColor tSwapC(intersectColors.at(j));
                    
                    intersectPoints.at(j) = intersectPoints.at(lowest);
                    intersectColors.at(j) = intersectColors.at(lowest);
                    intersectPoints.at(lowest) = tSwapP;
                    intersectColors.at(lowest) = tSwapC;
               }
			}*/
			for(int j = 0; j < (int)intersectPoints.size(); j++){
				int highest = j;
				for(int k = j; k < (int)intersectPoints.size(); k++){
					if(k != highest && intersectPoints.at(k).z > intersectPoints.at(highest).z){
						highest = k;
					}
				}
				if(highest != j){
                    Point3 tSwapP(intersectPoints.at(j));
                    MyColor tSwapC(intersectColors.at(j));
                    
                    intersectPoints.at(j) = intersectPoints.at(highest);
                    intersectColors.at(j) = intersectColors.at(highest);
                    intersectPoints.at(highest) = tSwapP;
                    intersectColors.at(highest) = tSwapC;
				}
			}
			//Add points to screen
			if(intersectPoints.size() > 0){
				screen.push_back(intersectColors.at(0));
			}
			else{
				screen.push_back(BG_COLOR);
			}
		 }
		 else{
			 //If the ray is outside of the bounding box, no intersections will occur
			 screen.push_back(BG_COLOR);
		 }
     }

	 //If a tone reproduction model was specified, apply it to the scene
	 if(toneModel != NULL){
		 toneModel->adjustScene(&screen);
	 }

     //Draw the screen.
     for(int i = 0; i < (int)screen.size(); i++){
          int x = i/(screenWidth);
          int y = i%(screenHeight);
          drawPixel(x, y, screen.at(i));
     }
}
